import {Editor, Element as SlateElement, Point, Range, Transforms} from "slate";
import {ReactEditor} from "slate-react";

const SHORTCUTS = {
  '*': 'list-item',
  '-': 'list-item',
  '+': 'list-item',
  '>': 'block-quote',
  '#': 'heading-one',
  '##': 'heading-two',
  '###': 'heading-three',
  '####': 'heading-four',
  '#####': 'heading-five',
  '######': 'heading-six',
  '```': 'code',
  '1.': 'ol-item',
  '[x]': 'todo-item',
  '---': 'hr'
};

const INLINE_SHORTCUTS = {
  '`': {
    type: 'code',
    before: '`',
    regex: /`{1,2}[^`](.*?)`{1,2}/g
  },
  '**': {
    type: 'bold',
    before: '**',
    regex: /\*\*[^`](.*?)\*\*/g
  },
  '*': {
    type: 'italic',
    before: '*',
    regex: /\*[^`](.*?)\*/g
  },
  '~~': {
    type: 'lineThrough',
    before: '~~',
    regex: /\~\~[^`](.*?)\~\~/g
  }
};

export const withShortcuts = editor => {
  const {deleteBackward, insertText} = editor;
  editor.isVoid = (node) => !!node.void;
  editor.insertText = text => {
    const {selection} = editor;

    if (text === ' ' && selection && Range.isCollapsed(selection)) {
      const {anchor} = selection;
      const block = Editor.above(editor, {
        match: n => Editor.isBlock(editor, n),
      });
      if (block && block[0].type !== 'paragraph') {
        insertText(text);
        return;
      }
      const path = block ? block[1] : [];
      const start = Editor.start(editor, path);
      const range = {anchor, focus: start};
      const beforeText = Editor.string(editor, range);
      const type = SHORTCUTS[beforeText];
      if (/!\[(.*?)\]\((.*?)\)/.test(beforeText)) {
        let result = beforeText.match(/!\[(.*?)\]\((.*?)\)/);
        Transforms.select(editor, range);
        Transforms.delete(editor);
        const newProperties = {
          type: 'image',
          url: result[2],
          void: true
        };
        Transforms.setNodes(editor, newProperties, {
          match: n => Editor.isBlock(editor, n),
        });
        return;
      }
      if (type) {
        Transforms.select(editor, range);
        Transforms.delete(editor);
        const newProperties = {
          type,
          value: {
            checked: true,
            endTime: '2020-09-01'
          },
        };
        Transforms.setNodes(editor, newProperties, {
          match: n => Editor.isBlock(editor, n),
        });

        if (type === 'list-item') {
          const list = {type: 'bulleted-list', children: []};
          Transforms.wrapNodes(editor, list, {
            match: n =>
              !Editor.isEditor(n) &&
              SlateElement.isElement(n) &&
              n.type === 'list-item',
          })
        }
        if (type === 'ol-item') {
          const list = {type: 'ol', children: []};
          Transforms.wrapNodes(editor, list, {
            match: n =>
              !Editor.isEditor(n) &&
              SlateElement.isElement(n) &&
              n.type === 'ol-item',
          })
        }

        if (type === 'todo-item') {
          const list = {
            type: 'todo-list',
            children: []
          };
          Transforms.wrapNodes(editor, list, {
            match: n =>
              !Editor.isEditor(n) &&
              SlateElement.isElement(n) &&
              n.type === 'todo-item',
          })
        }
        return
      } else {
        const textArray = beforeText.split('').reverse();
        let startPoint = null;
        let matchText = '';
        let type = null;
        for (let i = 0; i < textArray.length; i++) {
          matchText += textArray[i];
          if (!type) {
            const nextMatchText = matchText + textArray[i + 1];
            type = INLINE_SHORTCUTS[matchText] && !INLINE_SHORTCUTS[nextMatchText] ? INLINE_SHORTCUTS[matchText] : null;
          } else {
            if (!!type.regex.test(matchText)) {
              startPoint = {...anchor, offset: anchor.offset - i - 1};
              break;
            }
          }
        }

        if (startPoint) {
          const range = {anchor: startPoint, focus: anchor};
          if (range.anchor.offset < 0 || range.focus.offset < 0) {
            Transforms.insertText(editor,' ');
            return;
          }
          const text = Editor.string(editor, range);
          const matchCharLength = type.before.length;
          Transforms.select(editor, range);
          Transforms.delete(editor);
          // 这里最后留一个空格，矫正mark元素导致的光标错误。
          Transforms.insertText(editor,`${text.slice(matchCharLength, text.length - matchCharLength)} `);
          Transforms.select(editor, {
            ...range,
            focus: {
              ...anchor,
              offset: anchor.offset - type.before.length * 2
            }
          });
          Editor.addMark(editor, type.type, true);
          Transforms.collapse(editor, {
            edge: 'focus'
          });
          return;
        }
      }
    }
    insertText(text)
  };
  editor.deleteBackward = (...args) => {
    const {selection} = editor;

    if (selection && Range.isCollapsed(selection)) {
      const match = Editor.above(editor, {
        match: n => Editor.isBlock(editor, n),
      });

      if (match) {
        const [block, path] = match;
        const start = Editor.start(editor, path);

        if (
          !Editor.isEditor(block) &&
          SlateElement.isElement(block) &&
          'paragraph' !== block.type &&
          Point.equals(selection.anchor, start)
        ) {
          const newProperties = {
            type: 'paragraph',
          };
          Transforms.setNodes(editor, newProperties);

          if (block.type === 'list-item') {
            Transforms.unwrapNodes(editor, {
              match: n =>
                !Editor.isEditor(n) &&
                SlateElement.isElement(n) &&
                n.type === 'bulleted-list',
              split: true,
            })
          }

          if (block.type === 'todo-item') {
            Transforms.unwrapNodes(editor, {
              match: n =>
                !Editor.isEditor(n) &&
                SlateElement.isElement(n) &&
                n.type === 'todo-list',
              split: true,
            })
          }

          return
        }
      }

      deleteBackward(...args)
    }
  };
  editor.customNodeOnChange = (element, value = {}) => {
    const path = ReactEditor.findPath(editor, element);
    requestAnimationFrame(() => {
      Transforms.setNodes(editor, {value}, {at: path});
    });
  };

  editor.moveTo = (targetIndex, sourceIndex) => {
    Transforms.moveNodes(editor, {
      at: [targetIndex],
      to: [sourceIndex]
    });
  };
  return editor
};
